package org.coderjonny.messageConverter;

import com.alibaba.fastjson2.JSONObject;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import org.coderjonny.dto.Request;
import org.coderjonny.gson.GsonUtil;
import org.coderjonny.log.LogEntity;
import org.coderjonny.thread.ThreadLocalUtil;
import org.coderjonny.trace.TraceIdUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

public class CustomerMessageConverter extends AbstractHttpMessageConverter<Object> implements GenericHttpMessageConverter<Object> {
    private String application;

    public CustomerMessageConverter(String application) {
        this();
        this.application = application;
    }

    public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    private static final Logger LOGGER = LoggerFactory.getLogger("FILE_LOGGER");

    public CustomerMessageConverter() {
        super(MediaType.valueOf(MediaType.APPLICATION_JSON + ";charset=UTF-8"),
                new MediaType("application", "*+json", DEFAULT_CHARSET),
                MediaType.APPLICATION_OCTET_STREAM,MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"));
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return true;
    }

    @Override
    protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        TypeToken<?> typeToken = getTypeToken(clazz);
        return readTypeToken(clazz,typeToken,inputMessage);
    }

    @Override
    protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        LogEntity logEntity = null;
        try{
            logEntity = (LogEntity) ThreadLocalUtil.get(ThreadLocalUtil.LOG_ENTITY);
            if (logEntity == null){
                logEntity = new LogEntity();
                logEntity.setTraceId(TraceIdUtil.newTraceId());
            }
            if (o instanceof byte[]){
                StreamUtils.copy((byte[]) o, outputMessage.getBody());
                logEntity.setResponse(new String((byte[]) o,DEFAULT_CHARSET));
            }else{
                Charset charset = getCharset(outputMessage.getHeaders());
                OutputStreamWriter writer = new OutputStreamWriter(outputMessage.getBody(), charset);
                String returnStr = GsonUtil.toJson(o);
                logEntity.setResponse(o);
                writer.write(returnStr);
                writer.close();
            }
        }catch (Throwable e){
            LOGGER.error("MessageConverter写出异常：",e);
            logEntity.setThrowable(e);
        }finally {
            Long startTime = logEntity.getTime();
            logEntity.setTime(System.currentTimeMillis() - startTime);
            logEntity.log(LOGGER);
            ThreadLocalUtil.remove(ThreadLocalUtil.LOG_ENTITY);
            ThreadLocalUtil.remove(TraceIdUtil.TRACE_ID);
        }
    }

    @Override
    public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
        return this.canRead(mediaType);
    }

    @Override
    public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        TypeToken<?> typeToken = getTypeToken(type);
        return readTypeToken(contextClass,typeToken,inputMessage);
    }

    @Override
    public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
        return this.canWrite(mediaType);
    }

    @Override
    public void write(Object o, Type type, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        writeInternal(o,outputMessage);
    }

    protected TypeToken<?> getTypeToken(Type type) {
        return TypeToken.get(type);
    }

    private Object readTypeToken(Class<?> clazz, TypeToken<?> token, HttpInputMessage inputMessage) throws IOException {
        Reader json = new InputStreamReader(inputMessage.getBody(), getCharset(inputMessage.getHeaders()));
        // 初始化
        LogEntity logEntity = initContext();
        String jsonStr = null;
        try{
            // header读取

            // 读取字符串
            BufferedReader bufferedReader = new BufferedReader(json);
            String lines;
            StringBuilder sb = new StringBuilder();
            while ((lines = bufferedReader.readLine()) != null)
                sb.append(lines);
            jsonStr = sb.toString();
            Object returnObj = null;
            if (token.getRawType().equals(Request.class)){
                Request request = GsonUtil.fromJson(jsonStr, token.getType());
                if (StringUtils.isEmpty(request.getTraceId())){
                    request.setTraceId(logEntity.getTraceId());
                }else{
                    updateTraceId(request.getTraceId());
                }
                returnObj = request;
            }else{
                returnObj = GsonUtil.fromJson(jsonStr,token.getType());
            }
            logEntity.setRequest(returnObj);
            // .....
            return returnObj;
        }catch (JsonSyntaxException exception){
            LOGGER.error("MessageConverter读取Json异常：",exception);
            logEntity.setThrowable(exception);
            throw new HttpMessageNotWritableException("Could not write JSON: " + jsonStr);
        } catch (Throwable e){
            LOGGER.error("MessageConverter读取异常：",e);
            logEntity.setThrowable(e);
        }
        return null;
    }

    private LogEntity initContext(){
        String traceId = TraceIdUtil.newTraceId();
        MDC.put(TraceIdUtil.SLEUTH_TRACE_NAME,traceId);
        LogEntity logEntity = new LogEntity();
        logEntity.setTime(System.currentTimeMillis());
        logEntity.setTraceId(traceId);
        logEntity.setApplication(application);
        ThreadLocalUtil.set(TraceIdUtil.TRACE_ID,traceId);
        ThreadLocalUtil.set(ThreadLocalUtil.LOG_ENTITY,logEntity);
        return logEntity;
    }

    private void updateTraceId(String traceId){
        ThreadLocalUtil.set(TraceIdUtil.TRACE_ID,traceId);
        MDC.put(TraceIdUtil.SLEUTH_TRACE_NAME,traceId);
        LogEntity logEntity = (LogEntity) ThreadLocalUtil.get(ThreadLocalUtil.LOG_ENTITY);
        if (logEntity ==null){
            logEntity = new LogEntity();
            ThreadLocalUtil.set(ThreadLocalUtil.LOG_ENTITY,logEntity);
            LOGGER.warn("ThreadLocal没有LogEntity，新建一个：\r\n" + JSONObject.toJSONString(logEntity));
        }
        logEntity.setTraceId(traceId);
    }

    private Charset getCharset(HttpHeaders headers) {
        if (headers == null || headers.getContentType() == null || headers.getContentType().getCharset() == null) {
            headers.setContentType(MediaType.valueOf(MediaType.APPLICATION_JSON_VALUE+";charset=UTF-8"));
            return DEFAULT_CHARSET;
        }
        return headers.getContentType().getCharset();
    }


}
